package com.gframework.sqlparam;

import java.io.Serializable;

import com.gframework.lang.ObjectRange;

/**
 * 简单的带有bean的单表Sql操作条件封装类.<br>
 * 本类是{@link SqlParam}的扩充子类，在其基础上多了一个bean对象作为参数传递到sql操作中。
 * <p>
 * <strong>注意：只有作为参数传给方法的那个对象上的属性是有效的，而链上的其他条件组上的实体类是不起任何作用的。</strong>
 * </p>
 * 
 * @since 1.0.0
 * @author Ghwolf
 */
public class BeanSqlParam<T extends Serializable> extends SqlParam {

	private static final long serialVersionUID = -3019545703432915702L;
	/**
	 * bean对象
	 */
	private T pojo;

	private BeanSqlParam(T pojo, String innerOp) {
		super(innerOp);
		this.pojo = pojo;
	}

	/**
	 * 取得一个带一个bean实例的且内部条件用and连接的条件组封装对象
	 * 
	 * @param pojo POJO实例化对象
	 */
	public static <E extends Serializable> BeanSqlParam<E> and(E pojo) {
		return new BeanSqlParam<>(pojo, AND_STR);
	}

	/**
	 * 取得一个带一个bean实例的且内部条件用or连接的条件组封装对象
	 * 
	 * @param pojo POJO实例化对象
	 */
	public static <E extends Serializable> BeanSqlParam<E> or(E pojo) {
		return new BeanSqlParam<>(pojo, OR_STR);
	}

	/**
	 * 取得bean对象
	 * 
	 * @return 一个POJO实例化对象
	 */
	public T getPojo() {
		return this.pojo;
	}

	// lambda function

	/**
	 * 添加一个等值判断规则
	 * 
	 * @param function 供给型函数式接口，例如可以直接是给定pojo的getter方法。
	 * <br>参数不能为null，但是返回值可以是null，返回null则什么都不做
	 * <br>名称会自动去掉开头的"is"或"get"并进行驼峰转下换线命名规范转换。
	 * <br>你也可以通过在此方法上添加{@link javax.persistence.Column}注解来强制指定名称。
	 * <br>方法值则会作为条件参数。
	 * @return 返回当前调用对象
	 */
	public SqlParam eq(SerFunction<T, Object> function) {
		String name = LambdaColumnNameUtil.getColumnName(function);
		return eq(name, function.apply(this.pojo));
	}

	/**
	 * 添加一个不等判断规则
	 * 
	 * @param function 供给型函数式接口，例如可以直接是给定pojo的getter方法。
	 * <br>参数不能为null，但是返回值可以是null，返回null则什么都不做
	 * <br>名称会自动去掉开头的"is"或"get"并进行驼峰转下换线命名规范转换。
	 * <br>你也可以通过在此方法上添加{@link javax.persistence.Column}注解来强制指定名称。
	 * <br>方法值则会作为条件参数。
	 * @return 返回当前调用对象
	 */
	public SqlParam ne(SerFunction<T, Object> function) {
		String name = LambdaColumnNameUtil.getColumnName(function);
		return ne(name, function.apply(this.pojo));
	}

	/**
	 * 添加一个IN判断规则
	 * 
	 * @param function 供给型函数式接口，例如可以直接是给定pojo的getter方法。
	 * <br>参数不能为null，但是返回值可以是null，返回null则什么都不做
	 * <br>名称会自动去掉开头的"is"或"get"并进行驼峰转下换线命名规范转换。
	 * <br>你也可以通过在此方法上添加{@link javax.persistence.Column}注解来强制指定名称。
	 * <br>方法值则会作为条件参数。
	 * <br><strong>特别注意，此对象可以返回一个数组或者一个Collection集合接口的子类。也可以是一个单独的对象，此时会按照长度为1的数组来处理！</strong>
	 * @return 返回当前调用对象
	 */
	public SqlParam in(SerFunction<T, Object> function) {
		String name = LambdaColumnNameUtil.getColumnName(function);
		Object value = function.apply(this.pojo);
		if (value == null) return this;
		return in(name, value);
	}
	
	/**
	 * 添加一个NOT IN判断规则
	 *  
	 * @param function 供给型函数式接口，例如可以直接是给定pojo的getter方法。
	 * <br>参数不能为null，但是返回值可以是null，返回null则什么都不做
	 * <br>名称会自动去掉开头的"is"或"get"并进行驼峰转下换线命名规范转换。
	 * <br>你也可以通过在此方法上添加{@link javax.persistence.Column}注解来强制指定名称。
	 * <br>方法值则会作为条件参数。
	 * <br><strong>特别注意，此对象可以返回一个数组或者一个Collection集合接口的子类。也可以是一个单独的对象，此时会按照长度为1的数组来处理！</strong>
	 * @return 返回当前调用对象
	 */
	public SqlParam notIn(SerFunction<T, Object> function) {
		String name = LambdaColumnNameUtil.getColumnName(function);
		Object value = function.apply(this.pojo);
		if (value == null) return this;
		return notIn(name, value);
	}
	
	/**
	 * 添加一个LIKE判断规则
	 * 
	 * @param function 供给型函数式接口，例如可以直接是给定pojo的getter方法。
	 * <br>参数不能为null，但是返回值可以是null，返回null则什么都不做
	 * <br>名称会自动去掉开头的"is"或"get"并进行驼峰转下换线命名规范转换。
	 * <br>你也可以通过在此方法上添加{@link javax.persistence.Column}注解来强制指定名称。
	 * <br>方法值则会作为条件参数。
	 * @return 返回当前调用对象
	 */
	public SqlParam like(SerFunction<T, String> function) {
		String name = LambdaColumnNameUtil.getColumnName(function);
		return like(name, function.apply(this.pojo));
	}
	
	/**
	 * 添加一个NOT LIKE判断规则
	 * 
	 * @param function 供给型函数式接口，例如可以直接是给定pojo的getter方法。
	 * <br>参数不能为null，但是返回值可以是null，返回null则什么都不做
	 * <br>名称会自动去掉开头的"is"或"get"并进行驼峰转下换线命名规范转换。
	 * <br>你也可以通过在此方法上添加{@link javax.persistence.Column}注解来强制指定名称。
	 * <br>方法值则会作为条件参数。
	 * @return 返回当前调用对象
	 */
	public SqlParam notLike(SerFunction<T, String> function) {
		String name = LambdaColumnNameUtil.getColumnName(function);
		return notLike(name, function.apply(this.pojo));
	}
	
	/**
	 * 添加一个IS NULL判断规则
	 * 
	 * @param function 供给型函数式接口，例如可以直接是给定pojo的getter方法。
	 * <br>参数不能为null，仅用于获取名称，不用于获取值
	 * <br>名称会自动去掉开头的"is"或"get"并进行驼峰转下换线命名规范转换。
	 * <br>你也可以通过在此方法上添加{@link javax.persistence.Column}注解来强制指定名称。
	 * @return 返回当前调用对象
	 */
	public SqlParam isNull(SerFunction<T, Object> function) {
		return isNull(LambdaColumnNameUtil.getColumnName(function));
	}
	
	/**
	 * 添加一个IS NOT NULL判断规则
	 * 
	 * @param function 供给型函数式接口，例如可以直接是给定pojo的getter方法。
	 * <br>参数不能为null，仅用于获取名称，不用于获取值
	 * <br>名称会自动去掉开头的"is"或"get"并进行驼峰转下换线命名规范转换。
	 * <br>你也可以通过在此方法上添加{@link javax.persistence.Column}注解来强制指定名称。
	 * @return 返回当前调用对象
	 */
	public SqlParam isNotNull(SerFunction<T, Object> function) {
		return isNotNull(LambdaColumnNameUtil.getColumnName(function));
	}
	
	/**
	 * 添加一个小于{@code <}判断规则
	 * 
	 * @param function 供给型函数式接口，例如可以直接是给定pojo的getter方法。
	 * <br>参数不能为null，但是返回值可以是null，返回null则什么都不做
	 * <br>名称会自动去掉开头的"is"或"get"并进行驼峰转下换线命名规范转换。
	 * <br>你也可以通过在此方法上添加{@link javax.persistence.Column}注解来强制指定名称。
	 * <br>方法值则会作为条件参数。
	 * @return 返回当前调用对象
	 */
	public SqlParam lt(SerFunction<T, Object> function) {
		String name = LambdaColumnNameUtil.getColumnName(function);
		return lt(name, function.apply(this.pojo));
	}
	
	/**
	 * 添加一个小于等于{@code <=}判断规则
	 * 
	 * @param function 供给型函数式接口，例如可以直接是给定pojo的getter方法。
	 * <br>参数不能为null，但是返回值可以是null，返回null则什么都不做
	 * <br>名称会自动去掉开头的"is"或"get"并进行驼峰转下换线命名规范转换。
	 * <br>你也可以通过在此方法上添加{@link javax.persistence.Column}注解来强制指定名称。
	 * <br>方法值则会作为条件参数。
	 * @return 返回当前调用对象
	 */
	public SqlParam le(SerFunction<T, Object> function) {
		String name = LambdaColumnNameUtil.getColumnName(function);
		return le(name, function.apply(this.pojo));
	}
	
	/**
	 * 添加一个大于{@code >}判断规则
	 * 
	 * @param function 供给型函数式接口，例如可以直接是给定pojo的getter方法。
	 * <br>参数不能为null，但是返回值可以是null，返回null则什么都不做
	 * <br>名称会自动去掉开头的"is"或"get"并进行驼峰转下换线命名规范转换。
	 * <br>你也可以通过在此方法上添加{@link javax.persistence.Column}注解来强制指定名称。
	 * <br>方法值则会作为条件参数。
	 * @return 返回当前调用对象
	 */
	public SqlParam gt(SerFunction<T, Object> function) {
		String name = LambdaColumnNameUtil.getColumnName(function);
		return gt(name, function.apply(this.pojo));
	}
	
	/**
	 * 添加一个大于等于{@code >=}判断规则
	 * 
	 * @param function 供给型函数式接口，例如可以直接是给定pojo的getter方法。
	 * <br>参数不能为null，但是返回值可以是null，返回null则什么都不做
	 * <br>名称会自动去掉开头的"is"或"get"并进行驼峰转下换线命名规范转换。
	 * <br>你也可以通过在此方法上添加{@link javax.persistence.Column}注解来强制指定名称。
	 * <br>方法值则会作为条件参数。
	 * @return 返回当前调用对象
	 */
	public SqlParam ge(SerFunction<T, Object> function) {
		String name = LambdaColumnNameUtil.getColumnName(function);
		return ge(name, function.apply(this.pojo));
	}
	
	/**
	 * 添加一个BETWEEN AND判断规则
	 * 
	 * @param function 供给型函数式接口，例如可以直接是给定pojo的getter方法。
	 * <br>参数不能为null，但是返回值或ObjectRange的返回值可以是null，如果存在null则什么都不做
	 * <br>名称会自动去掉开头的"is"或"get"并进行驼峰转下换线命名规范转换。
	 * <br>你也可以通过在此方法上添加{@link javax.persistence.Column}注解来强制指定名称。
	 * <br>方法值则会作为条件参数。
	 * @return 返回当前调用对象
	 * @param <R> 泛型的目的保证start和end数据类型一致
	 */
	public <R extends Serializable> SqlParam betweenAnd(SerFunction<T, ObjectRange<R>> function) {
		String name = LambdaColumnNameUtil.getColumnName(function);
		ObjectRange<R> range = function.apply(this.pojo);
		if (range == null) {
			return this;
		}
		return betweenAnd(name, range.getStart(), range.getEnd());
	}

}
